<?php

include_once 'interface.php';
class mysqlstore_StoreInstaller implements StoreInstaller
{

	public $schema = array();

	public function __construct ($self)
	{
		$this->self = $self;
	} // end of __construct

	public function install ()
	{
		$self = $this->self;
		$status = TRUE;

		//$this->installDatabase($this->database);
		$this->installTables($this->database->getTables());
		return $status;
	} // end of function install
	public function installDatabase ($database)
	{
		$db = $this->self->db;
		$sql = $database->getSql();
		$db->query($sql);
	} // end of function installDatabase
	public function installTables ($tables)
	{
		$db = $this->self->db;
		foreach ($tables as $table) {
			debug('Create table "'.$table->getName() . '"');
			$sql = $table->getSql();
			$db->query($sql);
		}
	} // end of function installTables

	public function createDefaultSchema ()
	{
		$db = $this->self->db;
		$builder = new mysqlSchemaBuilder();
		$builder->setDb($db);
		$this->database = $builder->createDatabase($db->db_name);

		$this->buildCollectionsSchema();
	} // end of function createDefaultSchema
	public function createClass ($name, $definition, $index_definition=array(), $primary_key=array())
	/**
	 * создать новый класс атрибутов
	 *
	 * Kласс свойств может использоваться для быстрого поиска объектов.
	 * Класс свойств задает список атрибутов объектов, характерных для данного класса.
	 *
	 * Название класса $name должно быть строкой <=16 символов, которая начинается с
	 * буквы и содержать буквенно-цифровые символы.
	 *
	 * Логически класс описывается списком его атрибутов: [ name -> (type, type_definition), ..]
	 *
	 * Массив $definition[{name}]['fields'][{type}][{type_def}]
	 *     {name} определяется подобно $name выше
	 *     {type} название типа свойства. может быть 'string', 'boolean', 'integer'
	 *     {type_def} параметры типа, такие как
	 *        size -- максимальная длина значения
	 *
	 * @param string $name название типа свойства
	 * @param array $definition определение типа свойства
	 * @param mixed $indexes список индексов
	 * @access public
	 * @return void
	 */
	{
		$self = $this->self;
		$this->error = "";


		// добавим в список свойств ссылку на объект
		$oiddef['fields']['oid']['int'] = NULL;
		if (!is_array($primary_key)) $primary_key = array();
		if (!in_array('oid', $primary_key)) { array_unshift($primary_key, 'oid'); }
		$definition = array_merge_recursive($oiddef, $definition);

		$tbl_name = $name === 'objects' ? $name : 'class_'.$name;
		$tables[$tbl_name] = array();
		$tables[$tbl_name]['indexes'] = array();
		// DDL для создания таблицы
		while (list($attr_name, $attrdef) = each($definition['fields'])) {
			while (list($type_name, $typedef) = each($attrdef)) {

				$tables[$tbl_name]['fields'][$attr_name] = $typedef;
				switch ($type_name) {
				case 'string':
					if (isset($typedef['size'])) {
						$esize = intval($typedef['size']);
						$tables[$tbl_name]['fields'][$attr_name]['type'] = 'VARCHAR';
						$tables[$tbl_name]['fields'][$attr_name]['size'] = $esize;
						//$tables[$tbl_name]['indexes'][0][] = $attr_name;
					} else {
						$tables[$tbl_name]['fields'][$attr_name]['type'] = 'TEXT';
						//$tables[$tbl_name]['ft_indexes'][0][] = $attr_name;
					}
					break;
				case 'bool':
				case 'boolean':
					$tables[$tbl_name]['fields'][$attr_name]['type'] = 'BOOL';
					//$tables[$tbl_name]['indexes'][0][] = $attr_name;
					break;
				case 'int':
				case 'integer':
					$tables[$tbl_name]['fields'][$attr_name]['type'] = 'INTEGER';
					//$tables[$tbl_name]['indexes'][0][] = $attr_name;
					break;
				case 'datetime':
					$tables[$tbl_name]['fields'][$attr_name]['type'] = 'DATETIME';
					//$tables[$tbl_name]['indexes'][0][] = $attr_name;
					break;
				default:
					$tables[$tbl_name]['fields'][$attr_name] = $typedef;
					$tables[$tbl_name]['fields'][$attr_name]['type'] = $type_name;
					break;
				}

				if (isset($typedef['canbenull'])) {
					$tables[$tbl_name]['fields'][$attr_name]['canbenull'] = 1;
				}

			}
		}

		// теперь добавим индексы
		if ($index_definition) {
			while (list($key1, $idxdef)=each($index_definition)) {
				$i = count($tables[$tbl_name]['indexes']);
				$tables[$tbl_name]['indexes'][] = array();
				$idxtype = NULL;
				if (is_array($idxdef[0])) {
					$idxtype = $idxdef['type'];
					$idxdef = $idxdef[0];
				}
				while (list($key2, $attr_name)=each($idxdef)) {
					$tables[$tbl_name]['indexes'][$i][] = $attr_name;
				}
				$tables[$tbl_name]['index_types'][$i] = $idxtype;
			}
		}

		if ($primary_key) {
			$tables[$tbl_name]['primary_key'] = $primary_key;
		}

		$this->addTables($this->database, $tables);
	} // end of function createClass

	public function clean ()
	{
		$self = $this->self;
		$status = TRUE;

		//$this->cleanDatabase($this->database);
		$this->cleanTables($this->database->getTables());
		return $status;
	} // end of function clean
	public function cleanDatabase ($database)
	{
		$db = $this->self->db;
		$sql = $database->getCleanSql();
		$db->query($sql);
	} // end of function cleanDatabase
	public function cleanTables ($tables)
	{
		$db = $this->self->db;
		foreach ($tables as $table) {
			debug('Clean table "'.$table->getName() . '"');
			$sql = $table->getCleanSql();
			$db->query($sql);
		}
	} // end of function cleanTables

	public function purge ()
	{
		$self = $this->self;
		$status = TRUE;

		//$this->cleanDatabase($this->database);
		$this->purgeTables($this->database->getTables());
		return $status;
	} // end of function clean
	public function purgeTables ($tables)
	{
		$db = $this->self->db;
		foreach ($tables as $table) {
			debug('Purge table "'.$table->getName() . '"');
			$sql = $table->getDropSql();
			$db->query($sql);
		}
	} // end of function cleanTables

	public function addTables ($database, $tables)
	{
		$db = $this->self->db;
		$builder = new mysqlSchemaBuilder();
		$builder->setDb($db);
		$database = $this->database;

		foreach ($tables as $tbl_name => $tbl_def) {
			$table = $builder->createTable($tbl_name, $tbl_def);
			foreach ($tbl_def['fields'] as $f_name => $f_def) {
				$field = $builder->createField($f_name, $f_def);
				$builder->saveField($table, $field);
			}
			if (isset($tbl_def['primary_key'])) {
				$primary_key = $builder->createPrimaryKey($tbl_def['primary_key']);
				$builder->savePrimaryKey($table, $primary_key);
			}
			if (isset($tbl_def['indexes'])) {
				foreach ($tbl_def['indexes'] as $i_name => $i_def) {
					$i_type = isset($tbl_def['index_types'][$i_name]) ? $tbl_def['index_types'][$i_name] : NULL;
					$index = $builder->createIndex($i_name, $i_def, $i_type);
					$builder->saveIndex($table, $index);
				}
			}
			if (isset($tbl_def['ft_indexes'])) {
				foreach ($tbl_def['ft_indexes'] as $i_name => $i_def) {
					$index = $builder->createFullTextIndex('ft_'.$i_name, $i_def);
					$builder->saveFullTextIndex($table, $index);
				}
			}
			$builder->saveTable($database, $table);
		}

		return TRUE;
	} // end of function addTables

	public function buildCollectionsSchema ($code_dir=NULL)
	/**
	 * Создать схему БД на основе классов collection.
	 *
	 * @param mixed $code_dir корневая дириктория.
	 * @access public
	 * @return void
	 */
	{
		global $FU;
		include_once 'includes/type.php';
		$config_dir = $FU->dir->config;
		$td = TypeDomain::instance();
		$td->addLoader(new KarniTypeLoader("$config_dir/type.d"));
		$td->addLoader(new DocTypeLoader("$config_dir/conf.d/doctype.php"));

		// add default types
		$td->setType('atom'        , new TAtom());
		$td->setType('object'      , new TObject());
		$td->setType('collection'  , new TCollection(array()));
		$td->cloneType('string'    , 'atom');
		$td->cloneType('integer'   , 'atom');
		$td->cloneType('bool'      , 'atom');
		$td->aliasType('boolean'   , 'bool');
		$td->cloneType('text'      , 'atom');
		$td->cloneType('datetime'  , 'atom');
		$td->cloneType('timestamp' , 'atom');

		$dir = (isset($code_dir) ? $code_dir : $config_dir) .'/type.d';
		//$dir = dirname(dirname(__FILE__)).'/collections';
		if ($handle = opendir($dir)) {
			while (false !== ($file = readdir($handle))) {
				if ($file != "." && $file != "..") {
					if (substr($file, -4) === '.php') {
						$type = substr($file, 0, -4);
						$o = get_type($type);
						$fields = $o->getFields();
						if (has_type($o, 'collection') && $fields) {
							$info = array();
							$info['fields'] = array();
							foreach ($fields as $f) {
								$p = $f->meta;
								$name = $p['name'];
								$type = $p;
								if (isset($p['db'])) foreach ($p['db'] as $k=>$v) {
									$type[$k] = $v;
								}
								$type_name = $type['type'];
								$info['fields'][$name][$type_name] = $type;
							}
							$indexes = $o->indexes;
							$primary_key = $o->primary_key;
							$class_name = isset($o->meta['db']['name']) ? $o->meta['db']['name'] : $o->getName();
							//var_dump($class_name, $info, $indexes);
						  $this->createClass($class_name, $info, $indexes, $primary_key);
						}
					}
				}
			}
			closedir($handle);
		}
	} // end of function buildCollectionsSchema
	public function iface ($name)
	{
		if ($this instanceof $name) return $this;
		else return $this->self->iface($name);
	} // end of function iface

}

class mysqlSchemaDatabase
{

	public $tables = array();
	public $name   = NULL;

	public function __construct ($builder)
	{
		$this->factory = $builder;
	} // end of __construct
	public function setName ($name)
	{
		$this->name = $name;
	} // end of function setName
	public function getName ()
	{
		return $this->name;
	} // end of function setName
	public function getTables ()
	{
		return $this->tables;
	} // end of function getTables
	public function addTable ($table)
	{
		$table->setDatabase($this);
		$this->tables[$table->getName()] = $table;
	} // end of function addTable
	public function getSql ()
	{
		$db = $this->factory->getDb();
		$sql = 'CREATE DATABASE '.$db->quoteDatabase($this->getName());
		return $sql;
	} // end of function getSql
	public function getCleanSql ()
	{
		$db = $this->factory->getDb();
		$sql = 'DROP DATABASE '.$db->quoteDatabase($this->getName());
		return $sql;
	} // end of function getSql

}
class mysqlSchemaTable
{

	public $database = NULL;
	public $fields  = array();
	public $pk      = NULL;
	public $indexes = array();
	public $primary_key = NULL;
	public $name    = NULL;

	public function __construct ($builder)
	{
		$this->factory = $builder;
	} // end of __construct
	public function getName ()
	{
		return $this->name;
	} // end of function getName
	public function setName ($name)
	{
		$this->name = $name;
	} // end of function setName
	public function addIndex ($index)
	{
		$index->setTable($this);
		$this->indexes[] = $index;
	} // end of function addIndex
	public function getIndexes ()
	{
		return $this->indexes;
	} // end of function getIndexes
	public function setPrimaryKey ($key)
	{
		$this->primary_key = $key;
	} // end of function setPrimaryKey
	public function getPrimaryKey ()
	{
		return $this->primary_key;
	} // end of function getPrimaryKey
	public function addField ($field)
	{
		$field->setTable($this);
		$this->fields[$field->getName()] = $field;
	} // end of function addField
	public function getFields ()
	{
		return $this->fields;
	} // end of function getFields
	public function setDatabase ($db)
	{
		$this->database = $db;
	} // end of function setDatabase
	public function getSql ()
	{
		$fields = array();
		$db = $this->factory->getDb();
		foreach ($this->getFields() as $field) {
			$fields[] = $field->getSql();
		}
		$keys = array();
		$primary_key = $this->getPrimaryKey();
		if (isset($primary_key)) {
			$keys[] = $primary_key->getSql();
		}
		foreach ($this->getIndexes() as $key) {
			$keys[] = $key->getSql();
		}
		$sql = 'CREATE TABLE ' . $db->quoteTable($this->getName())
			. "\n(\n"
			.    implode(",\n", $fields)
			.    ($keys ? ",\n" . implode(",\n", $keys) : '')
			. "\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin \n";
		return $sql;
	} // end of function getSql
	public function getCleanSql ()
	{
		$fields = array();
		$db = $this->factory->getDb();
		/*
		$keys = array();
		foreach ($this->getIndexes() as $key) {
			$keys[] = $key->getCleanSql();
		}
		 */
		$sql = 'TRUNCATE TABLE ' . $db->quoteTable($this->getName());
		return $sql;
	} // end of function getSql
	public function getDropSql ()
	{
		$fields = array();
		$db = $this->factory->getDb();
		/*
		$keys = array();
		foreach ($this->getIndexes() as $key) {
			$keys[] = $key->getCleanSql();
		}
		 */
		$sql = 'DROP TABLE ' . $db->quoteTable($this->getName());
		return $sql;
	} // end of function getSql

}
class mysqlSchemaField
{

	public $table   = NULL;
	public $name    = NULL;
	public $type    = NULL;
	public $size    = NULL;
	//public $default = NULL;
	public $attributes = array();
	public $canbenull = NULL;
	public $factory = NULL;

	public function __construct ($builder)
	{
		$this->factory = $builder;
	} // end of __construct
	public function setSize ($size)
	{
		$this->size = $size;
	} // end of function setSize
	public function getSize ()
	{
		return $this->size;
	} // end of function getSize
	public function setName ($name)
	{
		$this->name = $name;
	} // end of function setName
	public function getName ()
	{
		return $this->name;
	} // end of function getName
	public function setTable ($table)
	{
		$this->table = $table;
	} // end of function setTable
	public function getTable ()
	{
		return $this->table;
	} // end of function getTable
	public function setType ($type)
	{
		$this->type = $type;
	} // end of function setType
	public function getType ()
	{
		return $this->type;
	} // end of function getType
	public function addAttribute ($attr)
	{
		$this->attributes[] = $attr;
	} // end of function addAttribute
	public function getAttributes ()
	{
		return $this->attributes;
	} // end of function getAttributes
	public function setCanBeNull ()
	{
		$this->canbenull = 1;
	} // end of function getCanBeNull
	public function getCanBeNull ()
	{
		return $this->canbenull;
	} // end of function getCanBeNull
	public function getSql ()
	{
		$db     = $this->factory->getDb();
		$length = $this->getSize();
		$has_default = property_exists($this, 'default');
		if ($has_default) {
			$default = $this->getDefault();
			if ($default === '') $default = "''";
			elseif ($default === NULL) $default = "NULL";
		}
		//else if (is_null($default)) $default = 'NULL';
		$attrs = $this->getAttributes();
		$canbenull = $this->getCanBeNull();
		$sql = $db->quoteField($this->name)
			. ' ' . $this->type
			. ($length ? '('.intval($length).')' : '')
			. ((isset($canbenull) || ($has_default && $this->getDefault() === NULL)) ? '' : ' NOT NULL' )
			. ($has_default ? ' DEFAULT ' . $default : '')
			. ($attrs ? ' ' . implode(' ', $attrs) : '')
			;
		return $sql;
	} // end of function getSql

	public function getDefault ()
	{
		return $this->default;
	} // end of function getDefault
	public function setDefault ($default)
	{
		$this->default = $default;
	} // end of function setDefault

}
class mysqlSchemaIndex
{

	public $name   = NULL;
	public $fields = array();
	public $factory = NULL;
	var $index_type = '';

	public function __construct ($builder)
	{
		$this->factory = $builder;
	} // end of __construct
	public function setName ($name)
	{
		$this->name = $name;
	} // end of function setName
	public function setType ($index_type)
	{
		$this->index_type = $index_type;
	} // end of function setName
	public function getName ()
	{
		return $this->name;
	} // end of function getName
	public function addField ($name)
	{
		$this->fields[] = $name;
	} // end of function addField
	public function getFields ()
	{
		return $this->fields;
	} // end of function getFields
	public function getTable ()
	{
		return $this->table;
	} // end of function getTable
	public function setTable ($table)
	{
		$this->table = $table;
	} // end of function setTable
	public function getSql ()
	{
		$db = $this->factory->getDb();
		$fields = array();
		foreach ($this->getFields() as $field) {
			$fields[] = $db->quoteField($field);
		}
		$sql = ($this->index_type ? " {$this->index_type} " : " INDEX ")
			. $db->quoteField($this->getTable()->getName() . '_' . $this->getName())
			. ' (' . implode(",", $fields) . ')'
			;
		return $sql;
	} // end of function getSql

}

class mysqlSchemaFullTextIndex
{

	public $name   = NULL;
	public $fields = array();
	public $factory = NULL;

	public function __construct ($builder)
	{
		$this->factory = $builder;
	} // end of __construct
	public function setName ($name)
	{
		$this->name = $name;
	} // end of function setName
	public function getName ()
	{
		return $this->name;
	} // end of function getName
	public function addField ($name)
	{
		$this->fields[] = $name;
	} // end of function addField
	public function getFields ()
	{
		return $this->fields;
	} // end of function getFields
	public function getTable ()
	{
		return $this->table;
	} // end of function getTable
	public function setTable ($table)
	{
		$this->table = $table;
	} // end of function setTable
	public function getSql ()
	{
		$db = $this->factory->getDb();
		$fields = array();
		foreach ($this->getFields() as $field) {
			$fields[] = $db->quoteField($field);
		}
		$sql = ' FULLTEXT ' . $db->quoteField($this->getTable()->getName() . '_' . $this->getName())
			. ' (' . implode(",", $fields) . ')'
			;
		return $sql;
	} // end of function getSql

}
class mysqlSchemaPrimaryKey
{

	public $name   = NULL;
	public $fields = array();
	public $factory = NULL;

	public function __construct ($builder)
	{
		$this->factory = $builder;
	} // end of __construct
	public function addField ($name)
	{
		$this->fields[] = $name;
	} // end of function addField
	public function getFields ()
	{
		return $this->fields;
	} // end of function getFields
	public function getTable ()
	{
		return $this->table;
	} // end of function getTable
	public function setTable ($table)
	{
		$this->table = $table;
	} // end of function setTable
	public function getSql ()
	{
		$db = $this->factory->getDb();
		$fields = array();
		foreach ($this->getFields() as $field) {
			$fields[] = $db->quoteField($field);
		}
		$sql = ' PRIMARY KEY '
			. ' (' . implode(",", $fields) . ')'
			;
		return $sql;
	} // end of function getSql

}

class mysqlSchemaFactory
{

	public function database ($builder)
	{
		return new mysqlSchemaDatabase($builder);
	} // end of function table
	public function table ($builder)
	{
		return new mysqlSchemaTable($builder);
	} // end of function table
	public function field ($builder)
	{
		return new mysqlSchemaField($builder);
	} // end of function field
	public function index ($builder)
	{
		return new mysqlSchemaIndex($builder);
	} // end of function index
	public function ftindex ($builder)
	{
		return new mysqlSchemaFullTextIndex($builder);
	} // end of function index
	public function primaryKey ($builder)
	{
		return new mysqlSchemaPrimaryKey($builder);
	} // end of function index

}

class mysqlSchemaBuilder
{

	public $db = NULL;

	public function setDb ($database)
	{
		$this->db = $database;
	} // end of function setDb
	public function getDb ()
	{
		return $this->db;
	} // end of function getDb

	public function createDatabase ($name, $defs=NULL)
	{
		$db = mysqlSchemaFactory::database($this);
		$db->setName($name);
		return $db;
	} // end of function createDatabase
	public function createTable ($name, $defs=NULL)
	{
		$table = mysqlSchemaFactory::table($this);
		$table->setName($name);
		return $table;
	} // end of function createTable
	public function saveTable ($database, $table)
	{
		$database->addTable($table);
	} // end of function storeTable
	public function createField ($name, $defs=NULL)
	{
		$field = mysqlSchemaFactory::field($this);
		$field->setName($name);
		if (isset($defs['type'])) $field->setType($defs['type']);
		if (isset($defs['size'])) $field->setSize($defs['size']);
		if (array_key_exists('default', $defs)) $field->setDefault($defs['default']);
		if (isset($defs['attributes'])) {
			foreach ($defs['attributes'] as $attr) {
				$field->addAttribute($attr);
			}
		}
		if (isset($defs['canbenull'])) $field->setCanBeNull();

		return $field;
	} // end of function createField
	public function saveField ($table, $field)
	{
		$table->addField($field);
	} // end of function saveField
	public function createIndex ($name, $defs, $type=NULL)
	{
		$index = mysqlSchemaFactory::index($this);
		$index->setName($name);
		if (is_array($defs)) {
			foreach ($defs as $field) $index->addField($field);
			if (isset($type)) $index->setType($type);
		}
		return $index;
	} // end of function createIndex
	public function saveIndex ($table, $index)
	{
		$table->addIndex($index);
	} // end of function saveIndex
	public function createFullTextIndex ($name, $defs)
	{
		$index = mysqlSchemaFactory::ftindex($this);
		$index->setName($name);
		if (is_array($defs)) {
			foreach ($defs as $field) $index->addField($field);
		}
		return $index;
	} // end of function createIndex
	public function saveFullTextIndex ($table, $index)
	{
		$table->addIndex($index);
	} // end of function saveIndex
	public function createPrimaryKey ($defs)
	{
		$key = mysqlSchemaFactory::primaryKey($this);
		if (is_array($defs)) {
			foreach ($defs as $field) $key->addField($field);
		}
		return $key;
	} // end of function createIndex
	public function savePrimaryKey ($table, $index)
	{
		$table->setPrimaryKey($index);
	} // end of function saveIndex

}


